package com.example.pro1.service.zhao.wxpay.impl;

import com.example.pro1.service.zhao.table.RefundInfoService;
import com.google.gson.Gson;
import com.example.pro1.config.zhao.WxPayConfig;
import com.example.pro1.entity.zhao.OrderInfo;
import com.example.pro1.entity.zhao.RefundInfo;
import com.example.pro1.enums.OrderStatus;
import com.example.pro1.enums.wxpay.WxApiType;
import com.example.pro1.enums.wxpay.WxNotifyType;
import com.example.pro1.enums.wxpay.WxTradeState;
import com.example.pro1.service.zhao.table.OrderInfoService;
import com.example.pro1.service.zhao.wxpay.WxPayService;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

@Service
@Slf4j
public class WxPayServiceImp implements WxPayService
{
    @Resource
    private  WxPayConfig wxPayConfig;
    @Resource
    private CloseableHttpClient wxPayClient;
    @Resource
    private OrderInfoService orderInfoService;
    @Resource
    private RefundInfoService refundsInfoService;

    //线程锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public Map<String, Object> nativePay(long productId,String chargeWay,long userId,Integer fee,String num,String beizhu,String addressId) throws Exception {

        OrderInfo orderInfo = orderInfoService.createOderByProductId(productId,chargeWay,userId,fee,num,beizhu,addressId);


        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
        // 请求body参数
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        paramsMap.put("appid", wxPayConfig.getAppid());//支付id
        paramsMap.put("mchid", wxPayConfig.getMchId());//商户id
        paramsMap.put("description", orderInfo.getTitle());//商品描述
        paramsMap.put("out_trade_no", orderInfo.getOrderNo());//订单编号
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));//公网服务器地址（接受通知）
        Map amountMap = new HashMap();//订单金额
        amountMap.put("total", orderInfo.getTotalFee());//总金额
        amountMap.put("currency", "CNY");//货币类型
        paramsMap.put("amount", amountMap);


        //将参数转换成json字符串
        String jsonParams = gson.toJson(paramsMap);

        log.info("请求参数：" + jsonParams);

        StringEntity entity = new StringEntity(jsonParams,"utf-8");

        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
       //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);

        try
        {
            String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
            int statusCode = response.getStatusLine().getStatusCode();//响应状态码
            if (statusCode == 200) { //处理成功
                log.info("成功, 返回结果 = " + bodyAsString);
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("成功");
            } else {
                log.info("Native下单失败,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);
                throw new IOException("request failed");
            }
           //响应结果
            Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);

             //二维码
            String codeUrl = resultMap.get("code_url");
            //添加二维码
            orderInfo.setCodeUrl(codeUrl);
            orderInfoService.saveCode_url(orderInfo);

            Map<String, Object> map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orderInfo.getOrderNo());
            return map;
        }
        finally {
            response.close();
        }


    }

    @Override
    public void processOrder(Map<String, Object> resource) {
        log.info("订单处理");
        log.warn("内容==>{}",resource.get("resource").toString());

        log.info("对订单返回信息解密");
        String text = null;
        try {
            text = decryptFromResources((Map<String, Object>) resource.get("resource"));
        } catch (GeneralSecurityException e) {
            log.info("解密错误");
            e.printStackTrace();
        }
        log.info("报文转换为map");
        Gson gson = new Gson();
        HashMap<String, Object> plaintext = gson.fromJson(text, HashMap.class);
        String out_trade_no = plaintext.get("out_trade_no").toString();
        //申请锁进行并发控制
        //避免函数重入造成数据混乱
        if (lock.tryLock())
        {
            try {

                //防止多次调用
                String orderStatus = orderInfoService.getOrderInfoStatus(out_trade_no);
                if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {
                    return;
                }
                //更新支付状态
                orderInfoService.updateOrderInfo(out_trade_no, OrderStatus.SUCCESS);//支付成功
            }finally {
                lock.unlock();
            }
        }
    }

    @Override
    public int cancelOrder(String order_no) throws Exception {
        //调用微信支付的关单接口
        int i=this.closeOrder(order_no);
        //删除数据库OR设置状态
        if(i>1)
            orderInfoService.updateOrderInfo(order_no,OrderStatus.CANCEL);
        return i;

    }

    @Override
    public String queryOrder(String order_no) throws IOException {
        log.info("订单号==>{}",order_no);
        String url=String.format(WxApiType.ORDER_QUERY_BY_NO.getType(),order_no);
        url=wxPayConfig.getDomain().concat(url).concat("?mchid="+wxPayConfig.getMchId());//带参
        HttpGet httpGet = new HttpGet(url);
        //组装请求体
        Gson gson=new Gson();
        httpGet.setHeader("Accept", "application/json");
        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpGet);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
            int statusCode = response.getStatusLine().getStatusCode();//响应状态码
            if (statusCode == 200) { //处理成功
                log.info("成功, 返回结果 = " + bodyAsString);
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("成功");
            } else {
                log.info("Native下单失败,响应码 = " + statusCode+ ",返回结果 = " +
                        bodyAsString);
            }
            return bodyAsString;
        } finally {
            response.close();
        }
    }

    private int closeOrder(String order_no) throws Exception {
        log.info("关单接口调用 订单号==>{}",order_no);
        //组装退单接口url
        String url=String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(),order_no);
        url=wxPayConfig.getDomain().concat(url);
        HttpPost httpPost = new HttpPost(url);
        //组装请求体
        Gson gson=new Gson();
        HashMap<String,String> para=new HashMap<>();
        para.put("mchid",wxPayConfig.getMchId());
        String Jsonpara=gson.toJson(para);
        log.info("请求参数==>{}",Jsonpara);

        //组装请求对象
        StringEntity entity = new StringEntity(Jsonpara,"utf-8");

        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);

        //对返回数据进行处理
        try
        {
            int statusCode = response.getStatusLine().getStatusCode();//响应状态码
            if (statusCode == 200) { //处理成功
                log.info("成功" );
                return 999;
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("成功");
                return 999;
            } else {
                log.info("Native取消单失败,响应码 = " + statusCode);
                return 0;
            }
        }
        finally {
            response.close();
        }
    }

    /*
    * 订单维护方法
    * 调用接口检查超市订单准确性
    * T:关单
    * F：修正数据库数据
    * */
    @Override
    public void checkOrderStatu(String order_no) throws IOException {
        log.info("维护订单==>{}",order_no);

        String rseult=this.queryOrder(order_no);
         Gson gson = new Gson();
         Map map=gson.fromJson(rseult,HashMap.class);
         Object tradeS=map.get("trade_state");
         log.warn(tradeS.toString());

         log.warn(WxTradeState.SUCCESS.getType());
         if(!WxTradeState.SUCCESS.getType().equals(tradeS.toString()))
         {
             log.info("核实，关单");
             orderInfoService.updateOrderInfo(order_no,OrderStatus.CLOSED);
         }
         else
         {
             log.info("修正");
             orderInfoService.updateOrderInfo(order_no,OrderStatus.SUCCESS);
         }

    }



    /**
     * 退款
     ** *@param orderNo
     * *@param reason
     * *@throws IOException
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void refund(String orderNo) throws Exception {
        log.info("创建退款单记录");
        //根据订单编号创建退款单
        RefundInfo refundsInfo = refundsInfoService.findRefundN(orderNo);

        log.info("调用退款API");
        //调用统一下单API
        String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
        HttpPost httpPost = new HttpPost(url);
        // 请求body参数
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        paramsMap.put("out_trade_no", orderNo);//订单编号
        paramsMap.put("out_refund_no", refundsInfo.getRefundNo());//退款单编号
        paramsMap.put("reason",refundsInfo.getReason());//退款原因
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址

        Map amountMap = new HashMap();
        amountMap.put("refund", refundsInfo.getRefund());//退款金额
        amountMap.put("total", refundsInfo.getTotalFee());//原订单金额
        amountMap.put("currency", "CNY");//退款币种
        paramsMap.put("amount", amountMap);

        //将参数转换成json字符串
        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数 ===> {}" + jsonParams);
        StringEntity entity = new StringEntity(jsonParams,"utf-8");
        entity.setContentType("application/json");//设置请求报文格式
        httpPost.setEntity(entity);//将请求报文放入请求对象
        httpPost.setHeader("Accept", "application/json");//设置响应报文格式

        // 完成签名并执行请求，并完成验签
        CloseableHttpResponse response = wxPayClient.execute(httpPost);

        try {
            //解析响应结果
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功, 退款返回结果 = " + bodyAsString);
            } else if (statusCode == 204) {
                log.info("成功");
            } else {
                throw new RuntimeException("退款异常, 响应码 = " + statusCode+ ", 退款返回结果 = " + bodyAsString);
            }
            //更新订单状态
            orderInfoService.updateOrderInfo(orderNo, OrderStatus.REFUND_PROCESSING);
            //更新退款单
            refundsInfoService.updateRefund(bodyAsString);
        } finally {
            response.close();
        }
    }


    @Override
    public void checkRefundStatu(String order_no) throws Exception {
        log.info("维护退款==>{}",order_no);

        String rseult=this.queryRefund(refundsInfoService.findRefundN(order_no).getRefundNo());
        Gson gson = new Gson();
        Map map=gson.fromJson(rseult,HashMap.class);
        Object tradeS=map.get("trade_statue");

        if(WxTradeState.REFUND.getType().equals(tradeS)) {
            log.info("核实，关单");
            //TODO 重新调用一次？
           // orderInfoService.updateOrderInfo(order_no,OrderStatus.CLOSED);
        }
        else
        {
            log.info("修正");
            orderInfoService.updateOrderInfo(order_no,OrderStatus.REFUND_SUCCESS);
            refundsInfoService.updateRefund(rseult);
        }


    }

    /**
     * 查询退款接口调用
     * @param refundNo
     * @return
     */
    @Override
    public String queryRefund(String refundNo) throws Exception {
        log.info("查询退款接口调用 ===> {}", refundNo);
        String url = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo);

        url = wxPayConfig.getDomain().concat(url);
        //创建远程Get 请求对象
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");
        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpGet);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功, 查询退款返回结果 = " + bodyAsString);
            } else if (statusCode == 204) {
                log.info("成功");
            } else {
                throw new RuntimeException("查询退款异常, 响应码 = " + statusCode+ ",查询退款返回结果 = " + bodyAsString);
            }
            return bodyAsString;
        } finally {
            response.close();
        }
    }

    @Override
    public void processRefund(Map<String,Object> resource)
    {
        log.warn(String.valueOf(resource));

        log.info("退款处理");

        log.info("对退款返回信息解密");
        String text = null;
        try {
            text = decryptFromResources((Map<String, Object>) resource.get("resource"));
        } catch (GeneralSecurityException e) {
            log.info("解密错误");
            e.printStackTrace();
        }
        log.info("报文转换为map");
        Gson gson = new Gson();
        HashMap<String, Object> plaintext = gson.fromJson(text, HashMap.class);
        String out_trade_no = (String) plaintext.get("out_trade_no");
        //申请锁进行并发控制
        //避免函数重入造成数据混乱
        if (lock.tryLock())
        {
            try {

                //防止多次调用
                String orderStatus = orderInfoService.getOrderInfoStatus(out_trade_no);
                if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)) {
                    return;
                }
                refundsInfoService.updateRefund(text);
            }finally {
                lock.unlock();
            }
        }
    }

    public   String decryptFromResources(Map map) throws GeneralSecurityException {
        log.info("进入解密工具类——AES-GCM方法");
        String ciptext=map.get("ciphertext").toString();//加密后的码
        String nonce=map.get("nonce").toString();//随机串
        String associated_data=map.get("associated_data").toString();//附加数据包（可能为空PS这个是提交支付请求是我们添加的数据，原样返回达成通信类似备注
        log.info("密文===>{}" ,ciptext);
        AesUtil aesUtil=new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        String txt=aesUtil.decryptToString(associated_data.getBytes(StandardCharsets.UTF_8),nonce.getBytes(StandardCharsets.UTF_8),ciptext);
        log.info("明文===>{}",txt);

        return txt;
    }

}
